package com.metaverse.backend.exception;

import com.metaverse.backend.utils.BusiResult;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatus;
import org.springframework.transaction.TransactionSystemException;
import org.springframework.validation.BindException;
import org.springframework.validation.FieldError;
import org.springframework.validation.ObjectError;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.servlet.ModelAndView;

import javax.servlet.http.HttpServletRequest;
import javax.validation.ConstraintViolation;
import javax.validation.ConstraintViolationException;
import javax.validation.Path;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

/**
 * 异常处理
 *
 */
@ControllerAdvice
@Slf4j
public class GlobalExceptionHandler {

    @ExceptionHandler(value = BusinessException.class)
    @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
    @ResponseBody
    public BusiResult serviceExceptionHandler(BusinessException e) {
        log.debug("业务逻辑错误", e);
        BusiResult busiResult = BusiResult.fail(HttpStatus.INTERNAL_SERVER_ERROR.value(), e.getMessage());
        return busiResult;
    }

    @ExceptionHandler({AuthenticationException.class})
    @ResponseStatus(HttpStatus.UNAUTHORIZED)
    @ResponseBody
    public BusiResult handleAuthenticationException(AuthenticationException e) {
        log.debug("授权失败错误", e);
        BusiResult busiResult = BusiResult.fail(HttpStatus.UNAUTHORIZED.value(), e.getMessage());
        return busiResult;
    }

    @ExceptionHandler(value = TransactionSystemException.class)
    @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
    @ResponseBody
    public BusiResult serviceExceptionHandler(TransactionSystemException e, HttpServletRequest request) {
        log.error("transactionError:" + request.getRequestURI(), e);
        String message = e.getMessage();
        try {
            if (e.getCause().getCause() instanceof ConstraintViolationException) {
                ConstraintViolationException violationException = (ConstraintViolationException) e.getCause()
                        .getCause();
                message = violationException.getConstraintViolations().stream()
                        .map(constraintViolation -> constraintViolation.getPropertyPath() + constraintViolation.getMessage())
                        .collect(Collectors.joining(","));
                log.error(message);
            }
        } catch (Exception ignore) {
            //ignore
        }
        BusiResult busiResult = BusiResult.fail(HttpStatus.INTERNAL_SERVER_ERROR.value(), message);
        return busiResult;
    }


    @ExceptionHandler({BindException.class, ConstraintViolationException.class, MethodArgumentNotValidException.class})
    @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
    @ResponseBody
    public BusiResult validateExceptionHandler(Exception e, HttpServletRequest request) {
        log.error("请求：{}发生异常：{}", request.getRequestURI(), e);

        // 错误信息
        StringBuilder sb = new StringBuilder("参数校验失败：");
        // 错误信息map
        Map<String, String> error = new HashMap<>();

        String msg = "";
        if (!(e instanceof BindException) && !(e instanceof MethodArgumentNotValidException)) {
            for (ConstraintViolation cv : ((ConstraintViolationException) e).getConstraintViolations()) {
                msg = cv.getMessage();
                sb.append(msg).append("；");

                Iterator<Path.Node> it = cv.getPropertyPath().iterator();
                Path.Node last = null;
                while (it.hasNext()) {
                    last = (Path.Node) it.next();
                }
                /*for(last = null; it.hasNext(); last = (Path.Node)it.next()) {
                }*/
                error.put(last != null ? last.getName() : "", msg);
            }
        } else {
            List<ObjectError> allErrors = null;
            if (e instanceof BindException) {
                allErrors = ((BindException) e).getAllErrors();
            } else {
                allErrors = ((MethodArgumentNotValidException) e).getBindingResult().getAllErrors();
            }
            // 拼接错误信息
            for (ObjectError oe : allErrors) {
                msg = oe.getDefaultMessage();
                sb.append(msg).append("；");
                if (oe instanceof FieldError) {
                    error.put(((FieldError) oe).getField(), msg);
                } else {
                    error.put(oe.getObjectName(), msg);
                }
            }
        }

        BusiResult busiResult = BusiResult.fail(HttpStatus.INTERNAL_SERVER_ERROR.value(), sb.toString());
        return busiResult;
    }


    /**
     *
     * 默认异常处理
     *
     * @param e
     * @param request
     * @return
     */
    @ExceptionHandler(value = Exception.class)
    @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
    @ResponseBody
    public Object serviceExceptionHandler(Exception e, HttpServletRequest request) {
        log.error("serverError:" + request.getRequestURI(), e);
        String acceptHeader = request.getHeader("Accept");
        if (acceptHeader != null && acceptHeader.contains("text/html")) {
            ModelAndView modelAndView = new ModelAndView("commons/500");
            StringWriter out = new StringWriter();
            PrintWriter writer = new PrintWriter(out);
            e.printStackTrace(writer);
            String trace = out.toString();
            trace = trace.replaceAll("\n", "<br>");
            modelAndView.addObject("trace", trace);
            return modelAndView;
        } else {
            Map<String, Object> map = new HashMap<>();
            String errorInfo = e.getMessage();
            //找到原始错误信息
            if(errorInfo != null && errorInfo.indexOf("nested exception") != -1 && e.getCause() != null){
                errorInfo = e.getCause().getMessage();
            }

            if(errorInfo == null){
                errorInfo = "未知错误-" + e.getClass().getSimpleName();
            }
            BusiResult busiResult = BusiResult.fail(HttpStatus.INTERNAL_SERVER_ERROR.value(), errorInfo);
            return busiResult;
        }
    }


}
